﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using AbstraX.ServerInterfaces;
using AbstraX.TypeMappings;
using AbstraX;
using AbstraX.Bindings;
using System.Collections;
using AbstraX.AssemblyInterfaces;
using CodeGenerationPipeline;
using AbstraX.Contracts;
using System.Diagnostics;
using Utils;
using AbstraX.AssemblyInterfaces;

namespace GeneratorLibrary
{
    public class GeneratorStackItem
    {
        public GeneratorBase GeneratorBase { get; set; }
        public ConstructType ConstructType { get; set; }
        public BaseType MemberVariable { get; set; }
    }

    public static class LibraryExtensions
    {
        private static Stack<GeneratorStackItem> generatorStack;
        private static GeneratorBase baseGenerator;

        static LibraryExtensions()
        {
            generatorStack = new Stack<GeneratorStackItem>();
        }

        public static void Push(this GeneratorBase generatorBase, ConstructType constructType, BaseType memberVariable = null)
        {
            generatorStack.Push(new GeneratorStackItem
            {
                GeneratorBase = generatorBase,
                ConstructType = constructType,
                MemberVariable = memberVariable
            });
        }

        public static void Pop(this GeneratorBase generatorBase)
        {
            generatorStack.Pop();
        }

        private static ConstructType ConstructType
        {
            get
            {
                var stackItem = generatorStack.Peek();
                var constructType = stackItem.ConstructType;

                return constructType;
            }
        }

        public static bool HasMemberVariable
        {
            get
            {
                var stackItem = generatorStack.Peek();
                var memberVariable = stackItem.MemberVariable;

                return memberVariable != null;
            }
        }

        public static string MemberVariable
        {
            get
            {
                var stackItem = generatorStack.Peek();
                var memberVariable = stackItem.MemberVariable;

                return memberVariable.Name.CamelCase() + "_internal";
            }
        }

        public static string MemberVariableDeclaration
        {
            get
            {
                var stackItem = generatorStack.Peek();
                var memberVariable = stackItem.MemberVariable;

                return memberVariable.FullyQualifiedName + " " + memberVariable.Name.CamelCase() + "_internal";
            }
        }

        private static GeneratorBase Generator
        {
            get
            {
                if (generatorStack.Count == 0)
                {
                    if (baseGenerator == null)
                    {
                        baseGenerator = new GeneratorBase();
                    }

                    return baseGenerator;
                }
                else
                {
                    var stackItem = generatorStack.Peek();
                    var generator = stackItem.GeneratorBase;

                    return generator;
                }
            }
        }

        public static string GetCollectionTypeSignature(this NavigationItem navigationItem, params string[] generics)
        {
            var type = navigationItem.PropertyType;
            var genericArguments = type.GenericArguments.Reverse();
            var genericsReplaceQueue = new Queue<string>(generics.Reverse());
            var signature = navigationItem.PropertyType.Name.Remove(navigationItem.PropertyType.Name.Length - 2) + "<";
            var genericsString = string.Empty;

            Debug.Assert(navigationItem.PropertyType.IsCollectionType);

            foreach (var genericType in genericArguments)
            {
                var genericTypeString = string.Empty;

                if (genericsReplaceQueue.Count > 0)
                {
                    genericTypeString = genericsReplaceQueue.Dequeue();
                }
                else
                {
                    genericTypeString = genericType.GetReferencedName();
                }

                genericsString = ", " + genericTypeString + genericsString;
            }

            if (genericsString.Length > 0)
            {
                signature += genericsString.Remove(0, 2) + ">";
            }
            else
            {
                signature = type.GetReferencedName();
            }

            return signature;
        }

        public static string GetName(this IMethodOperation operation)
        {
            return Generator.RaiseGetMethodOperationName(operation);
        }

        public static string GetName(this NavigationItem navigationItem)
        {
            return Generator.RaiseGetNavigationPropertyName(navigationItem);
        }

        public static string GetName(this IPropertyBinding binding)
        {
            return Generator.RaiseGetPropertyBindingPropertyName(binding);
        }

        public static string GetName(this IElement element)
        {
            if (element.DataType.IsCollectionType && !element.DataType.IsScalarType)
            {
                return GetCollectionTypeName(element.DataType);
            }
            else
            {
                return Generator.RaiseGetElementName(element);
            }
        }

        public static string GetName(this IAttribute attribute)
        {
            return Generator.RaiseGetAttributeName(attribute);
        }

        public static string GetName(this IElementBuild build)
        {
            if (build.Element.DataType.IsCollectionType && !build.Element.DataType.IsScalarType)
            {
                return GetCollectionTypeName(build.Element.DataType);
            }
            else
            {
                return Generator.RaiseGetBuildName(build);
            }
        }

        public static string GetName(this BaseType type)
        {
            if (type.IsCollectionType && !type.IsScalarType)
            {
                return GetCollectionTypeName(type);
            }
            else
            {
                return Generator.RaiseGetTypeName(type);
            }
        }

        public static string GetReferencedName(this IElement element)
        {
            if (element.DataType.IsCollectionType && !element.DataType.IsScalarType)
            {
                return GetReferencedCollectionTypeName(element.DataType);
            }
            else
            {
                return Generator.RaiseGetReferencedElementName(element);
            }
        }

        public static string GetReferencedName(this IElementBuild build)
        {
            if (build.Element.DataType.IsCollectionType && !build.Element.DataType.IsScalarType)
            {
                return GetReferencedCollectionTypeName(build.Element.DataType);
            }
            else
            {
                return Generator.RaiseGetReferencedBuildName(build);
            }
        }

        public static string GetReferencedName(this BaseType type)
        {
            if (type.IsCollectionType && !type.IsScalarType)
            {
                return GetReferencedCollectionTypeName(type);
            }
            else
            {
                return Generator.RaiseGetReferencedTypeName(type);
            }
        }

        public static string GetCollectionTypeName(this BaseType type, bool isReference = false)
        {
            var name = string.Empty;

            if (type.IsArray)
            {
                if (isReference)
                {
                    name = GetReferencedName(type.CollectionType) + "[]";
                }
                else
                {
                    name = GetName(type.CollectionType) + "[]";
                }
            }
            else if (type.GenericArguments.Length > 0)
            {
                var collectionType = string.Empty;

                if (isReference)
                {
                    collectionType = Generator.RaiseGetReferencedTypeName(type);
                }
                else
                {
                    collectionType = Generator.RaiseGetTypeName(type);
                }

                var index = collectionType.IndexOf("`");

                if (index != -1)
                {
                    collectionType = collectionType.Remove(index);
                }

                collectionType += "<";

                foreach (var arg in type.GenericArguments)
                {
                    if (isReference)
                    {
                        collectionType += GetReferencedName(arg) + ", ";
                    }
                    else
                    {
                        collectionType += GetName(arg) + ", ";
                    }
                }

                collectionType = collectionType.RemoveEnd(2);

                name = collectionType + ">";
            }
            else
            {
                if (isReference)
                {
                    name = Generator.RaiseGetReferencedTypeName(type);
                }
                else
                {
                    name = Generator.RaiseGetTypeName(type);
                }
            }

            return name;
        }

        public static string GetReferencedCollectionTypeName(this BaseType type)
        {
            return GetCollectionTypeName(type, true);
        }

        public static bool IsClassToBeBuilt(this IElement element)
        {
            if (element.Kind == DefinitionKind.Class)
            {
                if (element.DataType.IsCollectionType && !element.DataType.IsScalarType)
                {
                    return CollectionIsToBeBuilt(element);
                }
                else
                {
                    return Generator.RaiseElementIsToBeBuilt(element);
                }
            }
            else
            {
                return false;
            }
        }

        public static bool IsToBeBuilt(this IElement element, ElementIsToBeBuiltHandler elementIsToBeBuiltHandler, TypeIsToBeBuiltHandler typeIsToBeBuiltHandler)
        {
            var generator = Generator;
            var isToBeBuilt = false;

            if (element.DataType.IsCollectionType && !element.DataType.IsScalarType)
            {
                return CollectionIsToBeBuilt(element, typeIsToBeBuiltHandler);
            }
            else
            {
                elementIsToBeBuiltHandler(element, out isToBeBuilt);

                return isToBeBuilt;
            }
        }

        public static bool CollectionIsToBeBuilt(this IElement element, TypeIsToBeBuiltHandler typeIsToBeBuiltHandler)
        {
            var isToBeBuilt = false;
            var type = element.DataType;

            if (type.IsArray)
            {
                typeIsToBeBuiltHandler(type.CollectionType, out isToBeBuilt);
            }
            else if (type.GenericArguments.Length > 0)
            {
                typeIsToBeBuiltHandler(type, out isToBeBuilt);

                if (isToBeBuilt)
                {
                    return true;
                }

                foreach (var arg in type.GenericArguments)
                {
                    typeIsToBeBuiltHandler(arg, out isToBeBuilt);

                    if (isToBeBuilt)
                    {
                        return true;
                    }
                }
            }
            else
            {
                typeIsToBeBuiltHandler(type, out isToBeBuilt);
            }

            return isToBeBuilt;
        }

        public static bool IsToBeBuilt(this IElement element)
        {
            if (element.DataType.IsCollectionType && !element.DataType.IsScalarType)
            {
                return CollectionIsToBeBuilt(element);
            }
            else
            {
                return Generator.RaiseElementIsToBeBuilt(element);
            }
        }

        public static bool CollectionIsToBeBuilt(this IElement element)
        {
            var isToBeBuilt = false;
            var type = element.DataType;

            if (type.IsArray)
            {
                isToBeBuilt = Generator.RaiseTypeIsToBeBuilt(type.CollectionType);
            }
            else if (type.GenericArguments.Length > 0)
            {
                isToBeBuilt = Generator.RaiseTypeIsToBeBuilt(type);

                if (isToBeBuilt)
                {
                    return true;
                }

                foreach (var arg in type.GenericArguments)
                {
                    isToBeBuilt = Generator.RaiseTypeIsToBeBuilt(arg);

                    if (isToBeBuilt)
                    {
                        return true;
                    }
                }
            }
            else
            {
                isToBeBuilt = Generator.RaiseTypeIsToBeBuilt(type);
            }

            return isToBeBuilt;
        }

        public static void Render(this IPropertyBinding binding)
        {
            switch (ConstructType)
            {
                case ConstructType.Interface:
                    GeneratorBase.Interface.Render(Generator, binding);
                    break;
                case ConstructType.Class:
                    GeneratorBase.Class.Render(Generator, binding);
                    break;
                case ConstructType.Enum:
                    GeneratorBase.Enum.Render(Generator, binding);
                    break;
            }
        }

        public static void Render(this IMethodOperation operation)
        {
            switch (ConstructType)
            {
                case ConstructType.Interface:
                    GeneratorBase.Interface.Render(Generator, operation);
                    break;
                case ConstructType.Class:
                    GeneratorBase.Class.Render(Generator, operation);
                    break;
            }
        }

        public static void Render(this NavigationItem navigationItem)
        {
            switch (ConstructType)
            {
                case ConstructType.Interface:
                    GeneratorBase.Interface.Render(Generator, navigationItem);
                    break;
                case ConstructType.Class:
                    GeneratorBase.Class.Render(Generator, navigationItem);
                    break;
            }
        }

        public static string GetInheritsList(string[] list)
        {
            return list.ToCommaDelimitedList();
        }

        public static IEnumerable<TSource> DistinctBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
        {
            var seenKeys = new HashSet<TKey>();
            return source.Where(element => seenKeys.Add(keySelector(element)));
        }
    }
}
